//------------------------------------------------------------------------------
// KpiUtilities
//
// Contains re-usable KPI related utility functions.
//------------------------------------------------------------------------------
using System;
using System.Collections;
using System.Text;
using System.Windows.Forms;

namespace RetailAnalytic.Shell
{
	public static class KpiUtilities
	{
		/// <summary>
		/// Returns a collection of TreeNodes representing the display folders contained
		/// in the displayFolders parameter. These TreeNodes will be created if not found. 
		/// An empty array will be returned if displayFolders does not contain any display 
		/// folders.
		/// </summary>
		/// <param name="rootNodeCollection">nodes collection to use as the root of the KPI tree</param>
		/// <param name="displayFolders">the value of a DisplayFolders property</param>
		/// <param name="displayFolderImageIndex">the ImageIndex to use for a display folder tree node</param>
		/// <returns>
		/// An array of TreeNode objects representing the display folders contained in displayFolders. 
		/// May return an empty array but will not return null.
		/// </returns>
		public static TreeNode[] GetDisplayFolderTreeNodes( 
			TreeNodeCollection rootNodeCollection, 
			string displayFolders, 
			int displayFolderImageIndex )
		{
			if ( string.IsNullOrEmpty( displayFolders ) )
			{
				return new TreeNode[0];
			}
			else
			{
				string[][] parsedDisplayFolders = ParseDisplayFolders( displayFolders );
				int pathCount = parsedDisplayFolders.GetLength( 0 );
				TreeNode[] displayFolderNodes = new TreeNode[pathCount];
				for ( int pathIndex = 0; pathIndex < pathCount; pathIndex++ )
				{
					string[] path = parsedDisplayFolders[pathIndex];
					TreeNode folderNode = null;
					TreeNodeCollection parentNodeCollection = rootNodeCollection;
					foreach ( string folder in path )
					{
						TreeNode[] nodesFound = parentNodeCollection.Find( folder, false );
						if ( nodesFound.Length > 0 )
						{
							folderNode = nodesFound[0];
						}
						else
						{
							folderNode = parentNodeCollection.Add( folder, folder, displayFolderImageIndex, displayFolderImageIndex );
						}
						parentNodeCollection = folderNode.Nodes;
					}
					displayFolderNodes[pathIndex] = folderNode;
				}
				return displayFolderNodes;
			}
		}

		/// <summary>
		/// Parses a the value of a DisplayFolders property into an array of paths each of which is
		/// an array of folder names.
		/// </summary>
		/// <param name="displayFolders">string containing the value of a DisplayFolders property</param>
		/// <returns>
		/// An array of arrays of strings. The outer array may be zero-length, but will not be null.  
		/// The inner arrays will not be null or zero-length.  Duplicate paths will be removed.
		/// </returns>
		public static string[][] ParseDisplayFolders( string displayFolders )
		{
			const char pathDelimiter = ';';
			const char folderDelimiter = '\\';

			if ( displayFolders == null )
			{
				return new string[0][];
			}

			// Get paths
			int emptyPathCount = 0;
			string[] pathStrings = displayFolders.Split( pathDelimiter );
			string[][] allPaths = new string[pathStrings.Length][];
			for ( int i = 0; i < pathStrings.Length; i++ )
			{
				// Get folders
				int emptyFolderCount = 0;
				string[] allFolders = pathStrings[i].Split( folderDelimiter );
				for ( int j = 0; j < allFolders.Length; j++ )
				{
					allFolders[j] = allFolders[j].Trim();
					if ( allFolders[j].Length == 0 )
					{
						emptyFolderCount++;
					}
				}

				// Get currentPath without any empty folders
				string[] nonEmptyFolders;
				if ( emptyFolderCount == 0 )
				{
					// This is the common case so optimize
					nonEmptyFolders = allFolders; 
				}
				else if ( emptyFolderCount == allFolders.Length )
				{
					nonEmptyFolders = null;
					emptyPathCount++;
				} 
				else
				{
					nonEmptyFolders = new string[allFolders.Length - emptyFolderCount];
					for ( int j = 0, k = 0; j < allFolders.Length; j++ )
					{
						if ( allFolders[j].Length > 0 )
						{
							nonEmptyFolders[k] = allFolders[j];
							k++;
						}
					}
				}

				// Remove duplicate paths
				if ( nonEmptyFolders != null && IsPathAlreadyInPathsArray( allPaths, nonEmptyFolders, i ) )
				{
					nonEmptyFolders = null;
					emptyPathCount++;
				}

				allPaths[i] = nonEmptyFolders;
			}

			// Get results without any empty paths
			string[][] nonEmptyPaths;
			if ( emptyPathCount == 0 )
			{
				// This is the common case so optimize
				nonEmptyPaths = allPaths;
			}
			else if ( emptyPathCount == allPaths.Length )
			{
				nonEmptyPaths = new string[0][];
			}
			else
			{
				nonEmptyPaths = new string[allPaths.Length - emptyPathCount][];
				for ( int i = 0, j = 0; i < allPaths.Length; i++ )
				{
					if ( allPaths[i] != null )
					{
						nonEmptyPaths[j] = allPaths[i];
						j++;
					}
				}
			}
			return nonEmptyPaths;
		}

		/// <summary>
		/// Takes a normalized value such as those associated with KPI Status and Trend and returns
		/// a numeric index of an image based on the normalized value.
		/// </summary>
		/// <param name="normalizedValue">normalized value between -1 and 1 (values less than -1 are treated as -1 and those greater than 1 are treated as 1)</param>
		/// <param name="firstImageIndex">value of the first image index</param>
		/// <param name="lastImageIndex">value of the last image index</param>
		/// <returns>An integer between firstImageIndex and lastImageIndex, inclusive</returns>
		public static int GetImageIndex( double normalizedValue, int firstImageIndex, int lastImageIndex )
		{
			const double normalizedLowerBound = -1.0;
			const double normalizedUpperBound = 1.0;
			if ( double.IsNaN( normalizedValue ) )
			{
				return firstImageIndex;
			}
			else if ( normalizedValue <= -1 )
			{
				return firstImageIndex;
			}
			else if ( normalizedValue >= 1 )
			{
				return lastImageIndex;
			}
			else
			{
				const double inputRange = normalizedUpperBound - normalizedLowerBound;
				double outputRange = ( double )( Math.Abs( lastImageIndex - firstImageIndex ) + 1 );
				double outputSegmentsFromLowerBound = 
					( normalizedValue - normalizedLowerBound ) * ( outputRange / inputRange );
				outputSegmentsFromLowerBound = Math.Round( outputSegmentsFromLowerBound, 10 ); // round off floating point errors
				int zeroBasedIndex = ( int )(
					( normalizedValue > 0 ) ?
					Math.Floor( outputSegmentsFromLowerBound ) : 		// borders between segments (whole numbers) belong to the preceeding segment
					Math.Ceiling( outputSegmentsFromLowerBound ) - 1 );	// borders between segments (whole numbers) belong to the following segment

				return ( firstImageIndex < lastImageIndex ) ? 
					firstImageIndex + zeroBasedIndex : 
					firstImageIndex - zeroBasedIndex;
			}
		}

		#region ParseDisplayFolder helper methods

		/// <summary>
		/// Checks if paths contains currentPath before currentIndex.  Used by ParseDisplayFolders.
		/// </summary>
		private static bool IsPathAlreadyInPathsArray( string[][] paths, string[] currentPath, int currentIndex )
		{
			if ( paths == null )
			{
				return false;
			}

			int count = Math.Min( currentIndex, paths.GetLength( 0 ) );
			for ( int i = 0; i < count; i++ )
			{
				if ( DoPathsMatch( paths[i], currentPath ) )
				{
					return true;
				}
			}
			return false;
		}

		/// <summary>
		/// Checks if path1 and path2 match.  
		/// </summary>
		private static bool DoPathsMatch( string[] path1, string[] path2 )
		{
			if ( path1 == null && path2 == null )
			{
				return true;
			}
			else if ( path1 == null || path2 == null )
			{
				return false;
			}
			else if ( path1.Length != path2.Length )
			{
				return false;
			}
			else
			{
				for ( int i = 0; i < path1.Length; i++ )
				{
					if ( string.Compare( path1[i], path2[i], true, System.Globalization.CultureInfo.CurrentUICulture ) != 0 )
					{
						return false;
					}
				}
				return true;
			}
		}

		#endregion ParseDisplayFolder helper methods
	}
}
